package com.example.demoshiro.shiro;

import com.example.democommon.contasant.CommonConstant;
import com.example.demoshiro.exception.CustomException;
import com.example.demoshiro.tools.JwtUtil;
import com.example.demoshiro.tools.RedisUtil;
import com.example.demoshiro.tools.SpringContextUtils;
import com.example.service.SysResourcesService;
import com.example.service.SysUserService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * <p>
 * shiro自定义认证逻辑
 * </p>
 *
 * @author ross zhang
 * @version 1.0
 * @PackageName demo-one
 * @Package com.example.demoshiro.shiro
 * @since 2022/4/16 16:16
 */
@Component
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
    @Resource
    private SysUserService userService;
    @Resource
    private SysResourcesService resourcesService;
    @Lazy
    @Resource
    private RedisUtil redisUtil;

    /**
     * redis过期时间
     */
    @Value("${jwt.expire.time}")
    private long expireTime;

    /**
     * 设置对应的token类型
     * 必须重写此方法，不然Shiro会报错
     *
     * @param token 令牌
     * @return boolean
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }


    /**
     * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
     * 触发检测用户权限时才会调用此方法，例如checkRole,checkPermission
     *
     * @param principalCollection 身份信息
     * @return AuthorizationInfo 权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("===============Shiro权限认证开始============ [ roles、permissions]==========");
        SysUser user = (SysUser) SecurityUtils.getSubject().getPrincipal();
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("userid", user.getId());
        List<SysResources> resourcesList = resourcesService.loadUserResources(map);
        // 权限信息对象info,用来存放查出的用户的所有的角色（role）及权限（permission）
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        for (SysResources resources : resourcesList) {
            info.addStringPermission(resources.getResUrl());
        }
        return info;
    }

    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @SneakyThrows
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
        HttpServletResponse response = SpringContextUtils.getHttpServletResponse();
        //获取用户的输入的账号.
        String token = (String) authenticationToken.getCredentials();
        // 解密获得account，用于和数据库进行对比
        String username = JwtUtil.getUsername(token);
        if (!StringUtils.hasLength(username)) {
            throw new AuthenticationException(CommonConstant.TOKEN_IS_ERROR);
        }
        SysUser sysUser = this.userService.selectByUsername(username);
        if (null == sysUser) {
            request.getRequestDispatcher("/common/unauthorized/" + CommonConstant.TOKEN_IS_INVALID_MSG).forward(request, response);
            throw new CustomException(401, "该帐号不存在(The account does not exist.)");
        }
        // 校验token是否超时失效 & 或者账号密码是否错误
        if (!jwtTokenRefresh(token, username, sysUser.getPassword())) {
            request.getRequestDispatcher("/common/unauthorized/" + CommonConstant.TOKEN_IS_INVALID_MSG).forward(request, response);
            throw new CustomException(401, "该帐号不存在(The account does not exist.)");
        }
        return new SimpleAuthenticationInfo(sysUser, token, getName());

    }

    /**
     * JWTToken刷新生命周期 （实现： 用户在线操作不掉线功能）
     * 用新生成的Token替换掉旧的Token,
     *
     * @param userName
     * @param passWord
     * @return
     */
    public boolean jwtTokenRefresh(String token, String userName, String passWord) {
        String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
        if (StringUtils.hasLength(cacheToken)) {
            // 校验token有效性
            if (!JwtUtil.verify(cacheToken, userName, passWord)) {
                String newAuthorization = JwtUtil.sign(userName, passWord);
                // 设置超时时间
                redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + userName, newAuthorization, CommonConstant.TOKEN_EXPIRE_TIME);
                redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + userName, expireTime * 2 / 1000);
                log.debug("——————————用户在线操作，更新token保证不掉线—————————jwtTokenRefresh——————— " + token);
            }
            return true;
        }
        return false;
    }
}
